#!/usr/bin/env bash
# set -x
set +o monitor     # 关闭作业控制
set +o notify      # 关闭作业通知
exec 2>/dev/null   # 重定向所有输出

# 不满足条件时全部拦截
[[ $BASH_SOURCE == $0 ]] || { echo "错误: 不接受 source 内建执行"; return 1; }
[ -z $(command -v wget) ] && exec echo "错误: 找不到命令 wget"
[ -z $1 ] && exec echo "用法: $BASH_SOURCE [url1] [url2]... [url3]..."

# 默认值，这将允许从环境变量或局部变量更换
: ${SW_MAX_JOBS=10}                       # 默认最大并发数
: ${SW_MAX_BATCH=$SW_MAX_JOBS}            # 默认最大并发数
: ${SW_MODE=batch}                        # 任务分配模式

# 脚本内建，无需导出
SW_MIN_WIDTH=64                           # 显示进度条的最小宽度
SW_PROCESSED=0                            # 指示已处理的任务数量
SW_TOTAL_URLS=0                           # 总任务数量
SW_ARGS_2_TASK=()                         # 从参数获取的 URL 列表
SW_ALLTASK_STOP_SIGAL=false               # 所有任务停止信号
SW_TASK_CONT_SIGAL=false                  # 接续新任务信号
SW_TASKID_LIST=()                         # 任务ID列表
SW_TASKPID_LIST=()                        # 任务PID列表
SW_TASKNAME_LIST=()                       # 任务名称列表
SW_TASKERR_LIST=()                        # 任务异常列表
SW_TASK_LOG="${TMPDIR:-/tmp}/.shit-wget/task-logs"             # 下载日志目录

# SW_TASK_LOG=$HOME/.pwget/log            # 下载日志目录

if ! [[ "$SW_MODE" =~ ^(all|batch|cont)$ ]]; then
  exec echo "错误: SW_MODE 必须是 all|batch|cont"
fi

# 检查 SW_MAX_BATCH 是否为有效值
if [[ -n "$SW_MAX_BATCH" && ! "$SW_MAX_BATCH" =~ ^[0-9]+$ ]]; then
  exec echo "错误: SW_MAX_BATCH 必须是一个正整数"
fi

# 批量递归创建目录
for i in $SW_TASK_LOG;do
  [ -d $i ] || mkdir -p $i
done


# 多任务进度显示函数
# 参数: 任务数量，各个任务的当前进度和总进度
SW-List-TaskProcGress(){
  local task_count=$1
  shift
  
  # 隐藏光标
  printf "\e[?25l"
  
  # 显示每个任务的进度
  for ((i=1; i<=task_count; i++)); do
    local current_progress_var="\$1"
    local total_progress_var="\$2"
    shift 2
    
    local current_progress=${!current_progress_var}
    local total_progress=${!total_progress_var}
    
    # 计算百分比
    if [ $total_progress -eq 0 ]; then
      local percentage=0
    else
      local percentage=$((current_progress * 100 / total_progress))
    fi
    
    # 显示进度条
    SW-Print-ProcGress $percentage 20
    echo " $i: ${percentage}%"
  done
  
  # 移动光标到开始位置
  printf "\e[${task_count}A"
}

# 主要的多任务下载实现
__Main__(){
  # 保存所有URL
  SW_ARGS_2_TASK=($(awk '!seen[$0]++' <<<$@))
  SW_TOTAL_URLS=${#SW_ARGS_2_TASK[@]}
  SW_PROCESSED=0

  echo "需要下载${SW_TOTAL_URLS}个文件"
  echo "按 Ctrl + C 以终止所有任务..."
  
  if [[ "$SW_MODE" == "cont" ]]; then
    echo "开启连续下载模式，将同时进行 $SW_MAX_JOBS 个任务，每有任务完成将接续新任务"
    # 连续模式：初始启动最大并发数的任务
    local initial_jobs=$((SW_MAX_JOBS < SW_TOTAL_URLS ? SW_MAX_JOBS : SW_TOTAL_URLS))
    for ((i=0; i<initial_jobs; i++)); do
      SW-Create-Task "${SW_ARGS_2_TASK[i]}"
      SW_PROCESSED=$((i+1))
    done
    echo ${SW_TASKPID_LIST[@]}
    # 跟踪任务，该函数会在任务完成时添加新任务
    SW-TrackProc-PrintProGress
  else
    # 批处理模式
    if [[ -z "$SW_MAX_BATCH" || "$SW_MAX_BATCH" -eq 0 ]]; then
      SW_MAX_BATCH=$SW_TOTAL_URLS
    fi

    [[ "$SW_MAX_BATCH" -lt "${#SW_ARGS_2_TASK[@]}" ]] && 
    echo "开启分批下载，共$(((SW_TOTAL_URLS + SW_MAX_BATCH - 1) / SW_MAX_BATCH))批次"
    
    local batch_count=0
    while ((SW_PROCESSED < SW_TOTAL_URLS)); do
      # 清空上一批次的任务数组
      SW_TASKID_LIST=()
      SW_TASKPID_LIST=()
      
      # 计算当前批次需要处理的URL数量
      local batch_end=$((SW_PROCESSED + SW_MAX_BATCH))
      if ((batch_end > SW_TOTAL_URLS)); then
        batch_end=$SW_TOTAL_URLS
      fi
      
      # 处理当前批次
      for ((i=SW_PROCESSED; i<batch_end; i++)); do
        SW-Create-Task "${SW_ARGS_2_TASK[i]}"
      done
      
      # 移除PID和wget进程提示
      # echo "将启用${#SW_TASKPID_LIST[@]}个wget进程"
      # echo "PID: ${SW_TASKPID_LIST[@]}"
      
      # 执行当前批次的下载跟踪
      SW-TrackProc-PrintProGress
      
      # 更新已处理的URL数量
      SW_PROCESSED=$batch_end
      batch_count=$((batch_count + 1))
    done
  fi
}

# 生成下载任务
SW-Create-Task(){
  local url=$1
  local TASK_NAME="$(basename "$url")"
  local TASK_ID="$(date +%Y%m%d%H%M%S%N)_${TASK_NAME}"
  LANG=en_US.UTF-8 wget --show-progress $url -o "$SW_TASK_LOG/pwget_${TASK_ID}.log" &>/dev/null &
  local PID=$!
  SW_TASKPID_LIST+=("$PID")
  SW_TASKID_LIST+=("$TASK_ID")
  SW_TASKNAME_LIST+=("$TASK_NAME")
}

# 多进程等待
# 指定等待多个进程，在所有被等待进程结束后返回 true
SW-Wait-AllProc(){
  local argv=($(awk '!seen[$0]++' <<<"$@"))
  local wait=0
  local args=${#argv[@]}
  for i in ${argv[@]};do
    # 典型的进程查询方法
    kill -0 $i &>/dev/null || let wait++
  done
  if ((wait == args));then
    return 0
  else
    return 1
  fi
}

# 进程跟踪
# 当某个进程结束后删除该进程所占序列
# 使用 wait 内建命令跟踪退出状态，如为异常退出则另外追加到异常序列
SW-Get-Task-Return(){
  local __index=0
  for i in ${SW_TASKPID_LIST[@]};do
    if ! kill -0 $i &>/dev/null;then
      wait $i &>/dev/null || {
        SW_TASKERR_LIST+=($i)
        SW_TASK_CONT_SIGAL=true
      }
    else
      continue
    fi
    let __index++
  done
}

# 进度条制作，理想效果为根据终端宽度自适应
# 输入整数进度（%）和需让出的宽度，制作长度匹配的进度条；未指定让出宽度则占满终端宽度。
SW-Print-ProcGress(){
  # 进度参数
  if [[ -z $1 || ! $1 =~ ^[0-9]{1,3}$ ]];then
    local PGR=0
  else
    local PGR=$1
  fi
  # 宽度参数
  # eval 直接构建参数执行，简化结构
  eval "local PGR_W=${2:-0}"

  # 开始制作进度条
  # 获取宽度
  local TPUT_W=$(stty size -F /dev/tty | cut -d' ' -f2)
  local PR_COLS=$(((PGR * (TPUT_W - PGR_W - 2)) / 100))
  local PR_COLS1=$(((100 * (TPUT_W - PGR_W - 2)) / 100))
  local PR_COLS2=$((PR_COLS1 - PR_COLS))
  printf [
  printf '%*s' $PR_COLS | tr ' ' '#'
  printf '%*s' $PR_COLS2
  printf ]  
}

# 清除日志缓存
SW-Clear-TaskLog-Cache(){
    rm $SW_TASK_LOG/pwget_"$1".log &>/dev/null
    unset SW_TASKID_LIST[$2] SW_TASKPID_LIST[$2] SW_TASKNAME_LIST[$2]
    SW_TASKID_LIST=("${SW_TASKID_LIST[@]}")
    SW_TASKPID_LIST=("${SW_TASKPID_LIST[@]}")
    SW_TASKNAME_LIST=("${SW_TASKNAME_LIST[@]}")
    return 0
}

# 多任务信息显示
SW-TrackProc-PrintProGress(){
  # 仅在第一批次设置信号陷阱
  if ((batch_count == 0)); then
    trap SW_ALLTASK_STOP_SIGAL=true EXIT
    trap SW_ALLTASK_STOP_SIGAL=true SIGINT
  fi

  stty -echo
  printf "\e[?25l"

  # 跟踪下载任务
  while true;do
    
    # 如果所有队列完成，将清除可能残余的缓存数据
    SW-Wait-AllProc "${SW_TASKPID_LIST[@]}" && {
      rm $SW_TASK_LOG/pwget_*.log &>/dev/null
      break
    }

    # 制作进度信息
    local __sw_task_index=0
    while [ $__sw_task_index -lt ${#SW_TASKID_LIST[@]} ]; do
      local pwget_item="${SW_TASKID_LIST[__sw_task_index]}"
      local LOG=$(tail -n2 $SW_TASK_LOG/pwget_"${pwget_item}".log 2>/dev/null | head -1)
      
      # 获取下载信息元素
      local PWGET_INFO="$(sed 's/K100%/K 100%/' <<<${LOG:0:25})"
      # echo -e "\e[K$LOG"
      
      # 下载进度，%
      local PWGET_PR=$(awk '{print $2}' <<< $PWGET_INFO | grep -oE '^[0-9]+%$' | tr -d '%')
      [ -z $PWGET_PR ] && local PWGET_PR=--
      
      # 预计剩余时间
      local PGWET_ETA=$(awk '{print $4}' <<< $PWGET_INFO | grep -oE '^[0-9dhms]+$')
      [ -z $PGWET_ETA ] && local PGWET_ETA=--s
      
      # 已下载大小
      local PGWET_DLS=$(awk '{print $1}' <<< $PWGET_INFO | grep -oE '^[0-9K]+$')
      [ -z $PGWET_DLS ] && local PGWET_DLS=--K
      
      # 速度 s
      local PWGET_DLSP=$(awk '{print $3}' <<< $PWGET_INFO | grep -oE '^[0-9GMKB.]+$')
      [ -z $PWGET_DLSP ] && local PWGET_DLSP=--

      # 任务名称
      local PWGET_NAME="${SW_TASKNAME_LIST[__sw_task_index]}"
      ((`wc -L <<< $PWGET_NAME` > 8)) && PWGET_NAME=${PWGET_NAME:0:12}...
      
      # 固定宽度格式化信息
      local DL_INFO=$(printf "| %8s %8sB %8sB/s %3s%% [ETA:%4s]" "$PWGET_NAME" "$PGWET_DLS" "$PWGET_DLSP" "$PWGET_PR" "$PGWET_ETA")
      local INFO_W=$(($(wc -L <<<$DL_INFO) + 1))
      local PWGET_DL_INFO="$(SW-Print-ProcGress $PWGET_PR $INFO_W) $DL_INFO"
      echo -e "\e[?25l$PWGET_DL_INFO\e[K"

      SW-Get-Task-Return
      
      # 满进度时该任务退出队列
      if $SW_TASK_CONT_SIGAL || ((PWGET_PR == 100)) &>/dev/null;then
        SW-Clear-TaskLog-Cache "$pwget_item" $__sw_task_index
        printf "\r\e[K\e[1A"
        
        # 如果是连续模式且还有未处理的URL，添加新任务
        if [[ "$SW_MODE" == "cont" && $SW_PROCESSED -lt $SW_TOTAL_URLS ]]; then
          SW-Create-Task "${SW_ARGS_2_TASK[SW_PROCESSED]}"
          ((SW_PROCESSED++))
        fi
        # 重置接续信号
        $SW_TASK_CONT_SIGAL && SW_TASK_CONT_SIGAL=false
      else
        let __sw_task_index++
      fi
    done

    if $SW_ALLTASK_STOP_SIGAL;then
      stty echo
      for i in ${SW_TASKPID_LIST[@]};do
        kill -9 $i &>/dev/null
        printf "\r\e[K\e[1A"
      done
    else
      [[ $__sw_task_index -gt 0 ]] && printf "\r\e[K\e[%sA" "$__sw_task_index"
    fi
  done
  printf "\r\e[K\e[?25h\e[0m"
  stty echo
  $SW_ALLTASK_STOP_SIGAL && exec echo "[ Ctrl-C ] 进程已终止"
}

# 开始下载
__Main__ $@

__sw_errtask=${#SW_TASKERR_LIST[@]}
if (( __sw_errtask == 0 ));then
  echo "所有下载任务已完成"
else
  echo "有 $__sw_errtask 个下载任务异常退出"
  for i in ${SW_TASKERR_LIST[@]};do
    echo "任务进程 $i 异常退出"
  done
fi
